4.0
版本使用了PHP
+C
的双栈模式。创建协程时会创建一个C
栈,默认尺寸为2M
,创建一个PHP
栈,默认为8K
。
C
栈主要用于保存底层函数调用的局部变量数据,用于解决call_user_func
、array_map
等C
函数调用在协程切换时未能还原的问题。4.0
版本无论如何切换协程,底层总是能正确地切换回原先的C
函数栈帧继续向下执行。
4.0
底层还支持了嵌套关系,在协程内创建子协程,子协程挂起时仍然可以恢复父协程的执行。
Context::Context(size_t stack_size, coroutine_func_t fn, void* private_data) :
fn_(fn), stack_size_(stack_size), private_data_(private_data)
{
protect_page_ = 0;
end = false;
swap_ctx_ = NULL;
stack_ = (char*) sw_malloc(stack_size_);
swDebug("alloc stack: size=%u, ptr=%p.", stack_size_, stack_);
}
PHP
栈主要保存PHP
函数调用的局部变量数据,主要是zval
结构体,PHP
中标量类型,如整型、浮点型、布尔型等是直接保存在zval
结构体内的,而object
、string
、array
是使用引用计数管理,在堆上存储的。8K
的PHP
栈足以保存整个函数调用的局部变量。
static inline void sw_vm_stack_init(void)
{
uint32_t size = COROG.stack_size;
zend_vm_stack page = (zend_vm_stack) emalloc(size);
page->top = ZEND_VM_STACK_ELEMENTS(page);
page->end = (zval*) ((char*) page + size);
page->prev = NULL;
EG(vm_stack) = page;
EG(vm_stack)->top++;
EG(vm_stack_top) = EG(vm_stack)->top;
EG(vm_stack_end) = EG(vm_stack)->end;
}
C
栈切换使用了boost.context 1.60
汇编代码,用于保存寄存器,切换指令序列。主要是jump_fcontext
这个ASM
函数提供。PHP
栈的切换是跟随C
栈切同步进行的。底层会切换EG(vm_stack)
使得PHP
恢复到正确的PHP
函数栈帧。4.0.2
版本还增加了ob
输出缓存区的切换,ob_start
等操作也可以用于协程。
int sw_coro_resume(php_context *sw_current_context, zval *retval, zval *coro_retval)
{
coro_task *task = SWCC(current_task);
resume_php_stack(task);
if (EG(current_execute_data)->prev_execute_data->opline->result_type != IS_UNUSED && retval)
{
ZVAL_COPY(SWCC(current_coro_return_value_ptr), retval);
}
if (OG(handlers).elements)
{
php_output_deactivate();
if (!SWCC(current_coro_output_ptr))
{
php_output_activate();
}
}
if (SWCC(current_coro_output_ptr))
{
memcpy(SWOG, SWCC(current_coro_output_ptr), sizeof(zend_output_globals));
efree(SWCC(current_coro_output_ptr));
SWCC(current_coro_output_ptr) = NULL;
}
swTraceLog(SW_TRACE_COROUTINE, "cid=%d", task->cid);
coroutine_resume_naked(task->co);
if (unlikely(EG(exception)))
{
if (retval)
{
zval_ptr_dtor(retval);
}
zend_exception_error(EG(exception), E_ERROR TSRMLS_CC);
}
return CORO_END;
}
4.0
协程实现中,主协程即为Reactor
协程,负责整个EventLoop
的运行。在Swoole 4
版本中,可以同时使用协程和异步回调两种编程模式。异步回调的函数执行,将全部在主协程内执行。
除了执行异步回调之外,主协程还负责调度其他工作协程。
在工作协程中执行一些IO
操作时,底层会将IO
事件注册到EventLoop
,并让出执行权。
- 嵌套创建的非初代协程,会逐个让出到父协程,直到回到主协程
- 在主协程上创建的初代协程,会立即回到主协程
- 主协程的
Reactor
会继续处理IO
事件、Wait
监听新事件(epoll_wait
)
当主协程的Reactor
接收到新的IO
事件,会判断事件类型,执行不同的操作。如果是异步回调,会在主协程内直接执行。如果是属于某个挂起协程的事件,底层会挂起主协程,并恢复对应的工作协程。该工作协程挂起或退出时,会再次回到主协程。